{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 前言 - 学习本项目你可以获得什么\n",
    "- 入门百度智能云千帆AppBuilder，搭建一个工作流Agent应用\n",
    "- 使用AppBuilder-SDK进行工作流Agent对话\n",
    "- 了解如何使用AppBuilder-SDK简化多轮对话的开发"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. 项目背景\n",
    "\n",
    "### 1.1、 什么是AppBuilder\n",
    "[百度智能云千帆AppBuilder](https://appbuilder.cloud.baidu.com/)（以下简称AppBuilder）是基于大模型搭建AI原生应用的工作台，旨在降低AI原生应用的开发门槛，赋能开发者和企业快速实现应用搭建。\n",
    "\n",
    "平台提供了RAG（检索增强生成）、Agent（智能体）等应用框架，内置了文档问答、表格问答、多轮对话、生成创作等多种应用组件，还包括百度搜索和百度地图等特色组件，以及文本处理、图像处理和语音处理等传统AI组件，支持零代码、低代码、全代码三种开发方式，满足不同开发能力的开发者和企业的场景需求。\n",
    "\n",
    "### 1.2、 什么是AppBuilder-SDK\n",
    "\n",
    "[百度智能云千帆AppBuilder-SDK](https://github.com/baidubce/app-builder)(以下简称AB-SDK)，百度智能云千帆AppBuilder-SDK是百度智能云千帆AppBuilder面向AI原生应用开发者提供的一站式开发平台的客户端SDK。\n",
    "\n",
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/page2.png\" alt=\"drawing\" width=\"1000\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2. 创建工作流Agent应用并对话\n",
    "\n",
    "## 2.1 什么是工作流Agent?\n",
    "通过工作流编排的形式还原业务流程，每轮对话均严格按照工作流执行，提高了AI应用的可控性，并可编排出复杂业务流程，适用于客服、营销、生成、办公等高可控及高复杂度等场景。\n",
    "\n",
    "**工作流Agent无需设置角色人设，通过工作流编排的形式实现应用功能。**\n",
    "\n",
    "用户的所有对话均会触发此工作流处理，适用于严格按照流程执行的任务，例如：\n",
    "\n",
    "* 客服对话智能体，判断终端用户的意图后严格按照任务分支自动执行，无需大模型思考选择。\n",
    "* MBTI小助手，根据开发者编排的问题逐个提问，严格按照任务分支和结果执行后续的流程。\n",
    "\n",
    "工作流Agent支持用户配置工作流完成整个应用的对话过程，通过开始节点的Rawquery传入首轮对话，利用信息收集节点可以进行一组工作流中的多轮对话，最后以结束节点作为整个工作流结束的标志。开始节点和结束节点之前的完整流程构成一组工作流，每个信息收集节点都可以支持终端用户和应用的一次交互。\n",
    "\n",
    "## 2.2 创建工作流Agent应用-飞行客服小助手\n",
    "\n",
    "参考产品文档[飞行客服小助手](https://cloud.baidu.com/doc/AppBuilder/s/cm38k8nqr)创建示例应用。通过阅读本篇最佳实践，读者可以深度理解问答节点、信息处理节点、全局跳转节点的功能点和使用方式，搭建自己的工作流agent。对话效果如下：\n",
    "\n",
    "<p float=\"left\">\n",
    "  <img src=\"https://bce.bdstatic.com/doc/ai-cloud-share/AppBuilder/image_f11055c.png\" width=\"33%\" />\n",
    "  <img src=\"https://bce.bdstatic.com/doc/ai-cloud-share/AppBuilder/image_a680834.png\" width=\"33%\" /> \n",
    "  <img src=\"https://bce.bdstatic.com/doc/ai-cloud-share/AppBuilder/image_b602fef.png\" width=\"33%\" />\n",
    "</p>\n",
    "\n",
    "## 2.3 使用AppBuilder-SDK进行对话\n",
    "### 2.3.1 方式一：使用SDK直接对话（不推荐）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import appbuilder\n",
    "import os\n",
    "from appbuilder.core.console.appbuilder_client import data_class\n",
    "\n",
    "# 请前往千帆AppBuilder官网创建密钥，流程详见：https://cloud.baidu.com/doc/AppBuilder/s/Olq6grrt6#1%E3%80%81%E5%88%9B%E5%BB%BA%E5%AF%86%E9%92%A5\n",
    "# 设置环境变量\n",
    "os.environ[\"APPBUILDER_TOKEN\"] = \"...\"\n",
    "appbuilder.logger.setLoglevel(\"ERROR\")\n",
    "\n",
    "# 飞行客服小助手的应用id\n",
    "app_id = \"...\"\n",
    "# 初始化智能体\n",
    "client = appbuilder.AppBuilderClient(app_id)\n",
    "# 创建会话\n",
    "conversation_id = client.create_conversation()\n",
    "interrupt_ids = []\n",
    "\n",
    "msg = client.run(conversation_id, \"查天气\", stream=True)\n",
    "interrupt_event_id = None\n",
    "for ans in msg.content:\n",
    "    for event in ans.events:\n",
    "        if event.content_type == \"publish_message\":\n",
    "            print(event.detail.get(\"message\"))\n",
    "        if event.content_type == \"chatflow_interrupt\":\n",
    "            interrupt_event_id = event.detail.get(\"interrupt_event_id\")\n",
    "            break\n",
    "interrupt_ids.append(interrupt_event_id)\n",
    "\n",
    "msg2 = client.run(\n",
    "    conversation_id,\n",
    "    \"查航班\",\n",
    "    stream=True,\n",
    "    action=data_class.Action.create_resume_action(interrupt_event_id),\n",
    ")\n",
    "interrupt_event_id = None\n",
    "for ans in msg2.content:\n",
    "    for event in ans.events:\n",
    "        if event.content_type == \"publish_message\":\n",
    "            print(event.detail.get(\"message\"))\n",
    "        if event.content_type == \"chatflow_interrupt\":\n",
    "            interrupt_event_id = event.detail.get(\"interrupt_event_id\")\n",
    "            break\n",
    "        interrupt_ids.append(interrupt_event_id)\n",
    "\n",
    "msg3 = client.run(\n",
    "    conversation_id=conversation_id,\n",
    "    query=\"CA1234\",\n",
    "    stream=True,\n",
    "    action=data_class.Action.create_resume_action(interrupt_ids.pop()),\n",
    ")\n",
    "interrupt_event_id = None\n",
    "for ans in msg3.content:\n",
    "    for event in ans.events:\n",
    "        if event.content_type == \"text\":\n",
    "            print(event.detail.get(\"text\"))\n",
    "        if event.content_type == \"chatflow_interrupt\":\n",
    "            interrupt_event_id = event.detail.get(\"interrupt_event_id\")\n",
    "            break\n",
    "interrupt_ids.append(interrupt_event_id)\n",
    "\n",
    "msg4 = client.run(\n",
    "    conversation_id=conversation_id,\n",
    "    query=\"北京的\",\n",
    "    stream=True,\n",
    "    action=data_class.Action.create_resume_action(interrupt_ids.pop()),\n",
    ")\n",
    "has_multiple_dialog_event = False\n",
    "for ans in msg4.content:\n",
    "    for event in ans.events:\n",
    "        if event.content_type == \"text\":\n",
    "            print(event.detail.get(\"text\"))\n",
    "        if event.content_type == \"multiple_dialog_event\":\n",
    "            has_multiple_dialog_event = True\n",
    "            break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3.2 方式二 实现自己的EventHandler，更方便地进行对话(推荐)\n",
    "直接调用处理较为繁琐。SDK提供了使用AppBuilderEventHandler简化tool_call操作的功能。**继承AppBuilderEventHandler类，并实现针对各类型event的处理方法。**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import appbuilder\n",
    "from appbuilder.core.console.appbuilder_client.event_handler import (\n",
    "    AppBuilderEventHandler,\n",
    ")\n",
    "\n",
    "\n",
    "class MyEventHandler(AppBuilderEventHandler):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.interrupt_ids = []\n",
    "\n",
    "    def handle_content_type(self, run_context, run_response):\n",
    "        interrupt_event_id = None\n",
    "        event = run_response.events[-1]\n",
    "        if event.content_type == \"chatflow_interrupt\":\n",
    "            interrupt_event_id = event.detail.get(\"interrupt_event_id\")\n",
    "        if interrupt_event_id is not None:\n",
    "            self.interrupt_ids.append(interrupt_event_id)\n",
    "\n",
    "    def _create_action(self):\n",
    "        if len(self.interrupt_ids) == 0:\n",
    "            return None\n",
    "        event_id = self.interrupt_ids.pop()\n",
    "        return {\n",
    "            \"action_type\": \"resume\",\n",
    "            \"parameters\": {\"interrupt_event\": {\"id\": event_id, \"type\": \"chat\"}},\n",
    "        }\n",
    "\n",
    "    def run(self, query=None):\n",
    "        super().new_dialog(\n",
    "            query=query,\n",
    "            action=self._create_action(),\n",
    "        )\n",
    "\n",
    "\n",
    "def main():\n",
    "    # 请前往千帆AppBuilder官网创建密钥，流程详见：https://cloud.baidu.com/doc/AppBuilder/s/Olq6grrt6#1%E3%80%81%E5%88%9B%E5%BB%BA%E5%AF%86%E9%92%A5\n",
    "    # 设置环境变量\n",
    "    os.environ[\"APPBUILDER_TOKEN\"] = \"...\"\n",
    "    appbuilder.logger.setLoglevel(\"DEBUG\")\n",
    "\n",
    "    # 飞行客服小助手的应用id\n",
    "    app_id = \"...\"\n",
    "    # 初始化智能体\n",
    "    client = appbuilder.AppBuilderClient(app_id)\n",
    "    conversation_id = client.create_conversation()\n",
    "\n",
    "    event_handler = MyEventHandler()\n",
    "    event_handler.init(\n",
    "        appbuilder_client=client,\n",
    "        conversation_id=conversation_id,\n",
    "        stream=True,\n",
    "        query=\"查天气\",\n",
    "    )\n",
    "    for data in event_handler:\n",
    "        pass\n",
    "    event_handler.run(\n",
    "        query=\"查航班\",\n",
    "    )\n",
    "    for data in event_handler:\n",
    "        pass\n",
    "    event_handler.run(\n",
    "        query=\"CA1234\",\n",
    "    )\n",
    "    for data in event_handler:\n",
    "        pass\n",
    "    event_handler.run(\n",
    "        query=\"北京的\",\n",
    "    )\n",
    "    for data in event_handler:\n",
    "        pass\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3.3 方式三、实现自己的EventHandler，更方便地进行多轮对话（推荐）\n",
    "SDK提供了run_multiple_dialog_with_handler方法来进行多轮对话，按需传入iterable对象(queries、actions等参数)，即可一次调用完成多轮对话。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import appbuilder\n",
    "from appbuilder.core.console.appbuilder_client.event_handler import (\n",
    "    AppBuilderEventHandler,\n",
    ")\n",
    "\n",
    "\n",
    "class MyEventHandler(AppBuilderEventHandler):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.interrupt_ids = []\n",
    "\n",
    "    def handle_content_type(self, run_context, run_response):\n",
    "        interrupt_event_id = None\n",
    "        event = run_response.events[-1]\n",
    "        if event.content_type == \"chatflow_interrupt\":\n",
    "            interrupt_event_id = event.detail.get(\"interrupt_event_id\")\n",
    "        if interrupt_event_id is not None:\n",
    "            self.interrupt_ids.append(interrupt_event_id)\n",
    "\n",
    "    def _create_action(self):\n",
    "        if len(self.interrupt_ids) == 0:\n",
    "            return None\n",
    "        event_id = self.interrupt_ids.pop()\n",
    "        return {\n",
    "            \"action_type\": \"resume\",\n",
    "            \"parameters\": {\"interrupt_event\": {\"id\": event_id, \"type\": \"chat\"}},\n",
    "        }\n",
    "\n",
    "    def gen_action(self):\n",
    "        while True:\n",
    "            yield self._create_action()\n",
    "\n",
    "\n",
    "def main():\n",
    "    # 请前往千帆AppBuilder官网创建密钥，流程详见：https://cloud.baidu.com/doc/AppBuilder/s/Olq6grrt6#1%E3%80%81%E5%88%9B%E5%BB%BA%E5%AF%86%E9%92%A5\n",
    "    # 设置环境变量\n",
    "    os.environ[\"APPBUILDER_TOKEN\"] = \"...\"\n",
    "    appbuilder.logger.setLoglevel(\"DEBUG\")\n",
    "\n",
    "    # 飞行客服小助手的应用id\n",
    "    app_id = \"...\"\n",
    "    # 初始化智能体\n",
    "    client = appbuilder.AppBuilderClient(app_id)\n",
    "    conversation_id = client.create_conversation()\n",
    "\n",
    "    queries = [\"查天气\", \"查航班\", \"CA1234\", \"北京的\"]\n",
    "    event_handler = MyEventHandler()\n",
    "    event_handler = client.run_multiple_dialog_with_handler(\n",
    "        conversation_id=conversation_id,\n",
    "        queries=queries,\n",
    "        event_handler=event_handler,\n",
    "        stream=True,\n",
    "        actions=event_handler.gen_action(),\n",
    "    )\n",
    "    for data in event_handler:\n",
    "        for ans in data:\n",
    "            pass\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3.4 异步调用工作流Agent\n",
    "SDK提供异步调用工作流Agent的接口，下面是一个异步调用工作流Agent的实例。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Copyright (c) 2024 Baidu, Inc. All Rights Reserved.\n",
    "#\n",
    "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#\n",
    "#     http://www.apache.org/licenses/LICENSE-2.0\n",
    "#\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License.\n",
    "import os\n",
    "import asyncio\n",
    "import appbuilder\n",
    "from appbuilder.core.console.appbuilder_client.async_event_handler import (\n",
    "    AsyncAppBuilderEventHandler,\n",
    ")\n",
    "\n",
    "class MyEventHandler(AsyncAppBuilderEventHandler):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.interrupt_ids = []\n",
    "\n",
    "    async def handle_content_type(self, run_context, run_response):\n",
    "        interrupt_event_id = None\n",
    "        event = run_response.events[-1]\n",
    "        if event.content_type == \"chatflow_interrupt\":\n",
    "            interrupt_event_id = event.detail.get(\"interrupt_event_id\")\n",
    "        if interrupt_event_id is not None:\n",
    "            self.interrupt_ids.append(interrupt_event_id)\n",
    "\n",
    "    def _create_action(self):\n",
    "        if len(self.interrupt_ids) == 0:\n",
    "            return None\n",
    "        event_id = self.interrupt_ids.pop()\n",
    "        return {\n",
    "            \"action_type\": \"resume\",\n",
    "            \"parameters\": {\"interrupt_event\": {\"id\": event_id, \"type\": \"chat\"}},\n",
    "        }\n",
    "\n",
    "    async def run(self, query=None):\n",
    "        await super().new_dialog(\n",
    "            query=query,\n",
    "            action=self._create_action(),\n",
    "        )\n",
    "\n",
    "    def gen_action(self):\n",
    "        while True:\n",
    "            yield self._create_action()\n",
    "\n",
    "\n",
    "async def agent_run():\n",
    "    # 请前往千帆AppBuilder官网创建密钥，流程详见：https://cloud.baidu.com/doc/AppBuilder/s/Olq6grrt6#1%E3%80%81%E5%88%9B%E5%BB%BA%E5%AF%86%E9%92%A5\n",
    "    # 设置环境变量\n",
    "    os.environ[\"APPBUILDER_TOKEN\"] = \"...\"\n",
    "    appbuilder.logger.setLoglevel(\"DEBUG\")\n",
    "\n",
    "    # 飞行客服小助手的应用id\n",
    "    app_id = \"...\"\n",
    "    client = appbuilder.AsyncAppBuilderClient(app_id)\n",
    "    conversation_id = await client.create_conversation()\n",
    "    event_handler = MyEventHandler()\n",
    "    queries = [\"查天气\", \"查航班\", \"CA1234\", \"北京的\"]\n",
    "    event_handler = client.run_multiple_dialog_with_handler(\n",
    "        conversation_id=conversation_id,\n",
    "        queries=queries,\n",
    "        event_handler=event_handler,\n",
    "        stream=False,\n",
    "        actions=event_handler.gen_action(),\n",
    "    )\n",
    "    async for data in event_handler:\n",
    "        async for answer in data:\n",
    "            print(answer)\n",
    "\n",
    "    await client.http_client.session.close()\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    loop = asyncio.get_event_loop()\n",
    "    loop.run_until_complete(agent_run())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3、项目总结\n",
    "\n",
    "本项目最终完成了一个工作流Agent应用的创建，并使用Appbuilder-SDK进行对话功能。\n",
    "\n",
    "希望您可以不吝`Star`，给`AppBuilder-SDK`一些鼓励，期待您的`PR`，一起共建AIAgent生态。\n",
    "\n",
    "Github地址：https://github.com/baidubce/app-builder\n",
    "\n",
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/page10.png\" alt=\"drawing\" width=\"1000\"/>\n",
    "\n",
    "最后，您也可以进入`AppBuilder-SDK`的WX交流群，和大家一起交流AppBuilder使用及开发心得。\n",
    "\n",
    "<img src=\"https://chengmo-dev1.bj.bcebos.com/wechat_group.png\" alt=\"drawing\" width=\"1000\"/>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "testenv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
